/*
 * 代号：凤凰
 * http://www.jphenix.org
 * 2018年9月13日
 * V4.0
 */
package com.jphenix.servlet.filter;

import java.io.PrintWriter;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.servlet.FilterConfig;

import com.jphenix.clazz.ClassFile;
import com.jphenix.clazz.MethodInfo;
import com.jphenix.driver.json.Json;
import com.jphenix.driver.threadpool.ThreadSession;
import com.jphenix.kernel.baseobject.instanceb.ABase;
import com.jphenix.kernel.objectloader.interfaceclass.IBean;
import com.jphenix.kernel.objectloader.interfaceclass.IBeanRegister;
import com.jphenix.servlet.common.ActionContext;
import com.jphenix.share.printstream.PrintWriterTool;
import com.jphenix.standard.docs.BeanInfo;
import com.jphenix.standard.docs.ClassInfo;
import com.jphenix.standard.docs.Register;
import com.jphenix.standard.servlet.IActionBean;
import com.jphenix.standard.servlet.IFilter;
import com.jphenix.standard.servlet.IRequest;
import com.jphenix.standard.servlet.IResponse;
import com.jphenix.standard.viewhandler.IViewHandler;

/**
 * 传统Java动作类过滤器（存放在WEB-INF/clases中的动作类）
 * 
 * com.jphenix.servlet.filter.ClassicActionFilter
 * 
 * 不赞成使用这个过滤器。
 * 不赞成使用Eclipse开发动作类，有更简单的，不依赖开发环境的在线开发动作类，没必要使用古老的方式开发动作类
 * 这个过滤器的存在总是有理由的，客户强烈要求必须有这个功能，但是可以不用这种方式。（我是真不理解其中的原因）
 * 
 * 注意：能外部调用的方法必须是  public void 方法名(String 请求参数名) throws Exception {}
 * 要求：1. 必须是public的，且不能是static的
 *      2. 传入参数只能是字符串类型的
 *      3. 必须抛出异常，且异常只能是Exception
 *      4. 禁止出现同名方法
 *      5. 返回值必须位void类型
 *      
 * 外部调用路径： /类主键_方法名.do （注意：一律小写）   比如： /classa_methoda.do
 * 
 * 2019-06-15 按照IFilter增加了过滤器初始化方法
 * 2019-07-15 上传文件的根文件夹变为可配置的根文件夹
 * 
 * @author MBG
 * 2018年9月13日
 */
@ClassInfo({"2019-07-15 20:03","传统Java动作类过滤器"})
@BeanInfo({"classicactionfilter"})
@Register({"filtervector"})
public class ClassicActionFilter extends ABase implements IFilter,IBeanRegister {

	public String uploadBasePath = null; //上传文件根路径
	
	private Map<String,Object>       beanMap       = new HashMap<String,Object>();        //动作类对照容器          key动作路径 value动作类
	private Map<String,Method>       methodMap     = new HashMap<String,Method>();        //动作类对应方法容器  key动作路径  value方法对象
	private Map<String,String[]>     paraKeyMap    = new HashMap<String,String[]>();      //传入参数对照容器       key动作路径 value动作类
	private Map<String,List<String>> beanActionMap = new HashMap<String,List<String>>();  //类主键，动作路径对照容器  key:类主键 value:动作路径序列
	
	
	/**
	 * 构造函数
	 * @author MBG
	 */
	public ClassicActionFilter() {
		super();
	}


	/**
	 * 相应优先级
	 * 由小到大相应
	 */
	@Override
	public int getIndex() {
		return 180;
	}


	/**
	 * 相应扩展名
	 */
	@Override
	public String getFilterActionExtName() {
		return "do";
	}

	/**
	 * 相应请求
	 */
	@Override
	public boolean doFilter(IRequest req, IResponse resp) throws Exception {
        //获取动作对应的方法名
        Object time = log.runBefore(); //标记执行开始时间
	    //动作路径
        String action = req.getServletPath();
        //去掉扩展名
        int point = action.lastIndexOf(".");
        if(point>0) {
            action = action.substring(1,point);
        }
        if(uploadBasePath==null) {
        	uploadBasePath = infPath(p("file_sources")+"/upload");
        }
        //动作上下文
	    ActionContext actionContext = 
	            new ActionContext(
	                    req,resp,req.getServerName().toLowerCase()
	                    ,action,uploadBasePath);
	    
	    //放入动作路径和回话主键
	    ThreadSession.put("_action_",action);                            //动作路径
	    ThreadSession.put("_session_key_",actionContext.getSessionID()); //会话主键
	    
	    //获取指定的动作类
	    IActionBean ab = (IActionBean)beanMap.get(action);
	    if(ab==null) {
	    	return false;
	    }
	    //对应的方法对象
	    Method method = methodMap.get(action);
	    if(method==null) {
	    	return false;
	    }
	    ab.setActionContext(actionContext); //设置动作上下文
	    
	    boolean needExecute = true; //是否继续执行
	    if(needExecute) {
            try {
                //调用公共处理类的动作开始事件
                if(!ab.beforeRunAction()) {
                	needExecute = false;
                	
                	//以下注释都不对：因为在beforeRunAction中，也需要向页面输出内容，不能在这里做return
                	//比如在beforeRunAction方法中判断用户未登录，返回status
                	
                	
                	//scriptActionChargeCheck(action,actionContext,true); //释放锁定
            		//这个动作需要在公共父类中做，因为无法确定那么多公共父类中哪个做了哪个没做
                	//如果在公共父类中已经输出，在这里再执行一次就会抛异常
            		//actionContext.getResponse().sendError(403,actionContext.getRequest());
                    //return;
                }
            }catch(Exception e) {
                e.printStackTrace();
                needExecute = false;
                ab.errorAction(e);
            }
        }
        if(needExecute) {
            try {
                //调用动作
            	String[] keys = paraKeyMap.get(action);
            	if(keys==null) {
            		keys = new String[0];
            	}
                Object[] paraValues = new String[keys.length];
                for(int i=0;i<keys.length;i++) {
                	paraValues[i] = str(actionContext.getParameter(keys[i]));
                }
            	
                method.invoke(ab,paraValues); //调用目标方法
            	
            }catch(Exception e) {
                if(e instanceof InvocationTargetException) {
                    if(e.getCause()!=null) {
                        e.getCause().printStackTrace();
                    }else {
                        System.err.println("Invoke Script Exception Object:"+ab);
                    }
                }else {
                    e.printStackTrace();
                }
                needExecute = false;
                ab.errorAction(e);
            }
        }
        if(needExecute) {
            try {
                if(!ab.afterRunAction()) {
                	//如果执行该方法返回false，则不再继续执行后续程序。
                	//通常在父类中重写这个方法，在这个方法中已经将数据输出到界面，故返回false。
                	return true;
                }
            }catch(Exception e) {
                e.printStackTrace();
                needExecute = false;
                ab.errorAction(e);
            }
        }
        if(ab!=null && !ab.isNoReturnInfo()) {
            String       encoding;           //输出编码
            IViewHandler vh        = null;   //返回视图
            Json         reJson    = null;   //返回数据
            switch(ab.getOutContentType()) {
                case 0:
                    vh = ab.getReVh();
                    encoding = vh.getDealEncode();
                    if(encoding==null || encoding.length()<1) {
                        encoding = "UTF-8";
                    }
                    actionContext.getResponse().setHeader("Content-Type","text/html; charset="+encoding);
                    break;
                case 1:
                        vh = ab.getReXml();
                        if(vh!=null) {
                            encoding = vh.getDealEncode();
                             if(encoding==null || encoding.length()<1) {
                                    encoding = "UTF-8";
                                }
                        }else {
                            encoding = "UTF-8";
                        }
                        actionContext.getResponse().setHeader("Content-Type","text/xml; charset="+encoding);
                        break;
                default:
                    reJson = ab.getReJson();
                    actionContext.getResponse().setHeader("Content-Type","application/json; charset=UTF-8");
                    break;
            }
            
            
            boolean noOut = true; //是否没输出数据
            if(reJson!=null && !reJson.isEmpty()) {
                //设置JSON数据
                
            	if(!reJson.isList() && !reJson.containsKey("status")) {
            		//设置默认返回值
            		reJson.put("status","1");
            	}
            	
                /*
                 * jsonp 支持方法
                 * 如果存在这个参数值，则采用jsonp的返回格式
                 * 即：方法名(json返回内容);
                 * 
                 * 方法名是从request中，获取的  request.getParameter("jsoncallback");
                 * 
                 * jsoncallback 是写死的，目前还没有改变的必要
                 * 
                 */
                String jsonCallBack = actionContext.getParameter("jsoncallback");
                
                //输出流
                PrintWriter writer = actionContext.getResponse().getWriter();
                if(jsonCallBack.length()>0) {
                    debug("===============Jsonp Mode:[YES]===============");
                    debug(reJson.toString());
                    debug("(=======Return=======)");
                    writer.write(jsonCallBack+"(");
                    writer.write(reJson.toString());
                    writer.write(");");
                }else {
                    debug(reJson.toString());
                    debug("(=======Return=======)");
                    writer.write(reJson.toString());
                }
                noOut = false;
            }
            if(noOut && vh!=null) {
                //之前遇到个无法呈现的问题，偶尔输出内容全是问号
                //原来使用的是vh.getDealEncode();这回写死UTF-8
                //基本输出编码 weblogic只能用基本编码输出
                String standardOutEncoding = "UTF-8";
                try {
                    //设置编码格式
                    actionContext.getResponse().setCharacterEncoding(standardOutEncoding);
                }catch(NoSuchMethodError e) {
                    //使用WebLogic时会抛异常到这里，然后使用标准输出
                    //非weblogic不能使用标准输出，否则反而出现乱码
                    standardOutEncoding = "ISO-8859-1";
                }
                try {
                    //输出信息到页面
                    vh.getNodeBody(
                            new PrintWriterTool(
                                    actionContext.getResponse().getWriter()
                                    ,vh.getDealEncode()
                                    ,standardOutEncoding
                                    ,false));
                    //不能输出html代码到页面，太乱
                }catch(Exception e) {}
            }
        }
        log.writeRuntime(time,"Execute Complete Action:["+action+"]");
        outLog(true); //如果采用了线程池，需要手动重置这个值，否则就会出现有时无法输出日志的问题
		return true;
	}


	/**
	 * 绑定动作类
	 */
	@Override
	public boolean regist(Object bean) {
		if(bean==null || !(bean instanceof IActionBean)) {
			return false;
		}
		//返回类主键
		String beanId = ((IBean)bean).getBeanID();
		if(beanId==null || beanId.length()<1) {
			return false;
		}
		
		//方法信息容器 key：方法名  value：方法序列（包含同名方法）
		Map<String,List<Method>> mMap = new HashMap<String,List<Method>>();
		//获取全部方法数组
		Method[]     methods = bean.getClass().getDeclaredMethods();
		String       methodName; //方法名
		Class<?>[]   types;      //传参类型数组
		int          modifiers;  //方法的形态
		boolean      allString;  //传入参数是否都是字符串类型
		List<Method> mList;      //同名方法序列
		for(Method ele:methods) {
			modifiers = ele.getModifiers();
			if(!Modifier.isPublic(modifiers) 
					|| Modifier.isAbstract(modifiers)
					|| Modifier.isStatic(modifiers)
					|| !ele.getReturnType().equals(Void.TYPE)
					|| ele.getExceptionTypes().length!=1) {
				continue;
			}
			types      = ele.getParameterTypes();
			allString  = true;
			for(Class<?> typeEle:types) {
				if(typeEle!=String.class) {
					allString = false;
					break;
				}
			}
			if(!allString) {
				continue;
			}
			methodName = ele.getName();
			mList      = mMap.get(methodName);
			if(mList==null) {
				mList = new ArrayList<Method>();
				mMap.put(methodName,mList);
			}
			mList.add(ele);
		}
		
		try {
			//是否设置了动作路径
			boolean  hasSetAction        = false;
			//构建解析类
			ClassFile cf                 = new ClassFile(bean.getClass());
			//获取该类的全部方法对象序列
			List<MethodInfo> methodInfos = cf.getMethods();
			String action;           //动作路径
			int    paraCount;        //参数数量
			Method setMethod;        //需要设置的方法
			List<String> actionList; //动作路径序列
			for(MethodInfo ele:methodInfos) {
				methodName = ele.getName();
				if(!mMap.containsKey(methodName)) {
					continue;
				}
				action    = (beanId+"_"+ele.getName()).toLowerCase();
				mList     = mMap.get(methodName);
				paraCount = ele.getParameterCount();
				setMethod = null;
				for(Method mEle:mList) {
					if(mEle.getParameterTypes().length==paraCount) {
						setMethod = mEle;
						break;
					}
				}
				if(setMethod==null) {
					continue;
				}
				beanMap.put(action,bean);
				methodMap.put(action,setMethod);
				paraKeyMap.put(action,ele.getParameterNames());
				actionList = beanActionMap.get(beanId);
				if(actionList==null) {
					actionList = new ArrayList<String>();
					beanActionMap.put(beanId,actionList);
				}
				actionList.add(action);
				
				hasSetAction = true;
			}
			if(hasSetAction) {
				return true;
			}
		}catch(Exception e) {
			e.printStackTrace();
		}
		return false;
	}

	/**
	 * 解绑动作类
	 */
	@Override
	public void unRegist(Object bean) {
		if(bean==null || !(bean instanceof IActionBean)) {
			return;
		}
		//返回类主键
		String beanId = ((IBean)bean).getBeanID();
		if(beanId==null || beanId.length()<1) {
			return;
		}
		//该类中支持的动作路径序列
		List<String> actionList = beanActionMap.remove(beanId);
		if(actionList==null || actionList.size()<1) {
			return;
		}
		for(String action:actionList) {
			beanMap.remove(action);
			methodMap.remove(action);
			paraKeyMap.remove(action);
		}
	}
	
	/**
	 * 执行初始化
	 * @param fe         过滤器管理类
	 * @param config     Servlet配置信息类
	 * @throws Exception 异常（如果初始化发生异常，则放弃不再使用）
	 * 2019年6月15日
	 * @author MBG
	 */
	@Override
	public void init(FilterExplorer fe, FilterConfig config) throws Exception {}
}
